import java.util.*;
import org.antlr.v4.runtime.*;

public class Parser {
    public static final int
            FOR=1, IF=2, ELSE=3, ELSE_IF=4, WHILE=5, BREAK=6, Continue=7, T_DOUBLE=8,
            T_INT=9, WRITE=10, L_PAREN=11, R_PAREN=12, L_BRACKET=13, R_BRACKET=14,
            L_BRACE=15, R_BRACE=16, NO_LESS=17, NO_MORE=18, EQUAL=19, UN_EQUAL=20,
            BIGGER_THAN=21, LESS_THAN=22, LOG_NOT=23, LOG_OR=24, LOG_AND=25, PLUS=26,
            MINUS=27, MUL=28, DIV=29, MOD=30, NOT=31, SEMI=32, COMMA=33, ASSIGN=34,
            ID=35, INT=36, DOUBLE=37, ESCAPE=38, WS=39, NEW_LINE=40, B_COMMENT=41,
            L_COMMENT=42;


    protected List<CommonToken> tokens;
    protected static Iterator<CommonToken> iter;
    protected static CommonToken token;
    public AST ast;
    protected Stack<Node> S;
    protected Node current;
    public int level = 0;

    Parser(List t){
        tokens = t;
        iter = tokens.iterator();
        token = iter.next();
        S = new Stack<Node>();
        current = new Node(NodeType.prog,null,null,0, 0);
        S.push(current);
        ast = new AST(current);
    }
    public void visit(){
        ast.travPost(ast.root);
    }

    public Node newNode(NodeType t,String s,  Node p, Node c, int to, int l){
        Node x = new Node(t, s, p, c, to, l);
        p.child.add(x);
        if(c!=null){
            c.parent = x;
        }
        S.push(x);
        return x;
    }
    public Node newNode(NodeType t,  Node p, Node c, int to, int l){
        Node x = new Node(t, p, c, to, l);
        p.child.add(x);
        if(c!=null){
            c.parent=x;
        }
        S.push(x);
        return x;
    }
    public void rollBack(){
        --level;
        S.pop();
        current = S.peek();
    }

    public void prog(){
        stmt();
        while(iter.hasNext()){
            stmt();
        }
    }
    /** stmt    : declStmt
        | ifStmt
        | whileStmt
        //|breakStmt
        |(assignStmt SEMI)
        | writeStmt
        | stmtBlock
        | forStmt
        ;
*/
    protected void stmt(){
        current = newNode(NodeType.stmt, current, null, 0, ++level);
        switch(token.getType()){
            case T_INT:
            case T_DOUBLE:
                declStmt();
                break;
            case IF:
                ifStmt();
                break;
            case WHILE:
                whileStmt();
                break;
            case ID:
                assignStmt();
                match(SEMI);
                break;
            case WRITE:
                writeStmt();
                break;
            case L_BRACE:
                stmtBlock();
                break;
            case FOR:
                forStmt();
                break;
        }
        rollBack();
    }
    //declStmt: type deAs(COMMA deAs)* SEMI | type L_BRACKET INT R_BRACKET ID SEMI;
    protected void declStmt(){
        current = newNode(NodeType.declStmt, current, null, 0, ++level);
        type();
        if(token.getType()==L_BRACKET){
            match(L_BRACKET);
            match(INT);
            match(R_BRACKET);
            match(ID);
            match(SEMI);
            rollBack();
            return;
        }
        deAs();
        while(token.getType()==COMMA){
            match(COMMA);
            deAs();
        }
        match(SEMI);
        rollBack();
    }
    //deAs: ID | ID ASSIGN MINUS? expr;
    protected void deAs(){
        current = newNode(NodeType.deAs, current, null, 0, ++level);
        match(ID);
        if(token.getType()==ASSIGN){
            match(ASSIGN);
            if(token.getType()==MINUS) match(MINUS);
            expr();
        }
        rollBack();
    }
    //ifStmt: IF L_PAREN compare R_PAREN stmtBlock ( ELSE_IF L_PAREN compare R_PAREN stmtBlock)* (ELSE stmtBlock)*;
    protected void ifStmt(){
        current = newNode(NodeType.ifStmt, current, null, 0,++level);
        match(IF);
        match(L_PAREN);
        compare();
        match(R_PAREN);
        stmtBlock();
        while (token.getType()==ELSE_IF){
            match(ELSE_IF);
            match(L_PAREN);
            compare();
            match(R_PAREN);
            stmtBlock();
        }
        while (token.getType()==ELSE){
            match(ELSE);
            stmtBlock();
        }
        rollBack();
    }
    //whileStmt: WHILE L_PAREN compare R_PAREN stmtBlock;
    protected void whileStmt(){
        current = newNode(NodeType.whileStmt, current, null, 0,++level);
        match(WHILE);
        match(L_PAREN);
        compare();
        match(R_PAREN);
        stmtBlock();
        rollBack();
    }
    //assignStmt: variable ASSIGN MINUS? expr;
    protected void assignStmt(){
        current = newNode(NodeType.assignStmt, current, null, 0, ++level);
        variable();
        match(ASSIGN);
        if(token.getType()==MINUS) match(MINUS);
        expr();
        rollBack();
    }
    //writeStmt: WRITE L_PAREN expr R_PAREN SEMI;
    protected void writeStmt(){
        current = newNode(NodeType.writeStmt, current, null, 0,++level);
        match(WRITE);
        match(L_PAREN);
        expr();
        match(R_PAREN);
        match(SEMI);
        rollBack();
    }
    //stmtBlock: L_BRACE stmt* R_BRACE;
    protected void stmtBlock(){
        current = newNode(NodeType.stmtBlock, current, null, 0,++level);
        match(L_BRACE);
        while (token.getType()!=R_BRACE) stmt();
        match(R_BRACE);
        rollBack();
    }
    //forStmt : FOR L_PAREN((assignStmt SEMI)|declStmt) compare SEMI assignStmt R_PAREN stmtBlock;*/
    protected void forStmt(){
        current = newNode(NodeType.forStmt, current, null, 0, ++level);
        match(FOR);
        match(L_PAREN);
        switch (token.getType()){
            case T_DOUBLE:
            case T_INT:
                declStmt();
                break;
            case ID:
                assignStmt();
                match(SEMI);
                break;
        }
        compare();
        match(SEMI);
        assignStmt();
        match(R_PAREN);
        stmtBlock();
        rollBack();
    }
    //type: T_INT | T_DOUBLE;
    protected void type() {
        current = newNode(NodeType.type, current, null, 0, ++level);
        switch (token.getType()){
            case T_INT:
                match(T_INT);
                break;
            case T_DOUBLE:
                match(T_DOUBLE);
                break;
            default:
                System.out.print("Illeagal symbols: '" + token.getText() + "' at line: " + token.getLine() + " position: " + token.getCharPositionInLine() +". Symbols 'int', 'double' expected!\n");
        }
        rollBack();
    }
    //variable: ID | ID L_BRACKET (expr) R_BRACKET ;
    protected void variable(){
        current = newNode(NodeType.variable, current, null, 0, ++level);
        match(ID);
        if(token.getType()==L_BRACKET) {
            match(L_BRACKET);
            expr();
            match(R_BRACKET);
        }
        rollBack();
    }
    //constant: INT| DOUBLE ;
    protected void constant(){
        current = newNode(NodeType.constant, current, null, 0, ++level);
        switch (token.getType()){
            case INT:
                match(INT);
                break;
            case DOUBLE:
                match(DOUBLE);
                break;
            default:
                System.out.print("Illeagal symbols: '"+token.getText()+"' at line: "+token.getLine()+" position: "+token.getCharPositionInLine()+". Integer or Double expected!\n");
        }
        rollBack();
    }
    //compare :  UN_EQUAL compare | expr RELATION expr | L_PAREN compare R_PAREN ;
    protected void compare(){
        current = newNode(NodeType.compare, current, null, 0, ++level);
          switch (token.getType()){
              case L_PAREN:
                  match(L_PAREN);
                  compare();
                  match(R_PAREN);
                  break;
              case UN_EQUAL:
                  match(UN_EQUAL);
                  compare();
                  break;
              default:
                  expr();
                  relation();
                  expr();
          }
          rollBack();
    }
    //RELATION : BIGGER_THAN|LESS_THAN|NO_LESS|NO_MORE|EQUAL|UN_EQUAL;
    protected void relation(){
        current = newNode(NodeType.relation, current, null, 0, ++level);
        switch (token.getType()){
            case BIGGER_THAN:
                match(BIGGER_THAN);
                break;
            case LESS_THAN:
                match(LESS_THAN);
                break;
            case NO_LESS:
                match(NO_LESS);
                break;
            case NO_MORE:
                match(NO_MORE);
                break;
            case EQUAL:
                match(EQUAL);
                break;
            case UN_EQUAL:
                match(UN_EQUAL);
                break;
            default:
                System.out.print("Illeagal symbols: '"+token.getText()+"' at line: "+token.getLine()+" position: "+token.getCharPositionInLine()+". Symbols'>=', '<=', '==', '!=', '>', '<' expected!\n");
                break;
        }
        rollBack();
    }
    /**
     * expr  : expr1 expr2
     * expr1 : expr4 expr3
     * expr2 : (Plus|Minus) expr1 expr2
     * expr3 : (Mul|Div|Mod) expr4 expr3
     * expr4 : variable | constant | L_PAREN expr R_PAREN
     */
    protected void expr(){
        current = newNode(NodeType.expr, current, null, 0, ++level);
        expr1();
        expr2();
        rollBack();
        return;
    }
    protected void expr1(){
        current = newNode(NodeType.expr1, current, null, 0, ++level);
        expr4();
        expr3();
        rollBack();
    }
    protected  void expr2(){
        switch (token.getType()){
            case PLUS:
                current = newNode(NodeType.expr2, current, null, 0, ++level);
                match(PLUS);
                break;
            case MINUS:
                current = newNode(NodeType.expr2, current, null, 0, ++level);
                match(MINUS);
                break;
            default:
                return;
        }
        expr1();
        expr2();
        rollBack();
    }
    protected  void expr3(){
        switch (token.getType()){
            case MUL:
                current = newNode(NodeType.expr3,current, null, 0, ++level);
                match(MUL);
                break;
            case DIV:
                current = newNode(NodeType.expr3,current, null, 0, ++level);
                match(DIV);
                break;
            case MOD:
                current = newNode(NodeType.expr3,current, null, 0, ++level);
                match(MOD);
                break;
            default:
                return;
        }
        expr4();
        expr3();
        rollBack();
    }
    protected  void expr4(){
        current = newNode(NodeType.expr4, current, null, 0, ++level);
        if (token.getType()==INT||token.getType()==DOUBLE) constant();
        if (token.getType()==ID)  variable();
        if (token.getType()==L_PAREN) {
            match(L_PAREN);
            expr();
            match(R_PAREN);
        }
        rollBack();
    }
    protected void match(int type){
        if(token.getType()==type) {
            current = newNode(NodeType.terminal,token.getText(), current, null, type, ++level);
            if(iter.hasNext()) token = iter.next();
        } else {
            System.out.print("Illeagal symbols: '"+token.getText()+"' at line: "+token.getLine()+" position: "+token.getCharPositionInLine()+".\n");
        }
        rollBack();
    }
}
